/*
 * Copyright (C) 2008 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.esm.sm800.util;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.Handler;
import android.os.SystemClock;
import android.widget.ImageView;
import android.widget.TextView;

import com.esm.sm800.R;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;

//指纹识别全局管理
public final class ZazManager {

    private static final String TAG = ZazManager.class.getSimpleName();

    private static ZazManager zazManager;

    private final Context context;

    public static void init(Context context) {
        if (zazManager == null) {
            zazManager = new ZazManager(context);
        }
    }

    public static ZazManager getInstance() {
        return zazManager;
    }

    public Context getContext() {
        return context;
    }

    DevComm m_devComm;
    int m_nMaxFpCount = 500;

    public int m_nUserID;
    int m_nImgWidth, m_nImgHeight;
    long m_nPassedTime;
    String m_strPost;

    byte[] m_binImage;
    byte[] m_bmpImage;

    public TextView m_txtStatus = null; //信息输出
    public ImageView m_FpImageViewer = null;//图片---不使用

    private ZazManager(Context context) {
        this.context = context;
        m_binImage = new byte[1024 * 100];
        m_bmpImage = new byte[1024 * 100];

        if (m_devComm == null) {
            m_devComm = new DevComm(context, m_IConnectionHandler);
        }
    }

    //打开串口
    public void openDevice() {
        //串口名
        String szDevice = "/dev/ttyS2";
        //波特率
        int baudrate = 115200;

        String[] w_strInfo = new String[1];
        if (m_devComm != null) {
            if (!m_devComm.IsInit()) {
                if (m_devComm.OpenComm(szDevice, baudrate) == false) {
                    showMsg("Failed init device!");
                    return;
                }
            }
            if (m_devComm.Run_TestConnection() == DevComm.ERR_SUCCESS) {
                if (m_devComm.Run_GetDeviceInfo(w_strInfo) == DevComm.ERR_SUCCESS) {
                    showMsg("Open Success!\r\nDevice Info : " + w_strInfo[0]);
                } else {
                    showMsg("Can not connect to device!");
                }
            } else {
                showMsg("Can not connect to device!");
                m_devComm.CloseComm();
            }
        }
    }

    void showMsg(String msg) {
        if (m_txtStatus != null) {
            m_txtStatus.setText(msg);
        }
    }

    //关闭串口
    public void closeDevice() {
        m_devComm.CloseComm();
    }

    //使用用户ID注册指纹模板
    public void enroll() {
        int w_nRet;
        int[] w_nState = new int[1];

        if (!m_devComm.IsInit())
            return;

        if (!CheckUserID())
            return;

        // Check if fp is exist
        w_nRet = m_devComm.Run_GetStatus(m_nUserID, w_nState);

        if (w_nRet != DevComm.ERR_SUCCESS) {
            showMsg(GetErrorMsg(w_nRet));
            return;
        }

        if (w_nState[0] == DevComm.GD_TEMPLATE_NOT_EMPTY) {
            showMsg("Template is already exist");
            return;
        }

//        showMsg(context.getString(R.string.press_finger) + m_nUserID);
        showMsg(context.getString(R.string.press_finger));
        m_devComm.Run_SLEDControl(1);

        new Thread(new Runnable() {
            int w_nRet, w_nUserID, w_nEnrollStep = 0, w_nGenCount = 3;
            int[] w_nDupID = new int[1];
            int[] w_nWidth = new int[1];
            int[] w_nHeight = new int[1];

            @Override
            public void run() {

                w_nUserID = m_nUserID;

                while (w_nEnrollStep < w_nGenCount) {
                    m_strPost = String.format(context.getString(R.string.input_finger_d), w_nEnrollStep + 1);
                    if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);

                    // Capture
                    if (Capturing() < 0)
                        return;

                    m_strPost = context.getString(R.string.release_your_finger);
                    if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);

                    // Up Cpatured Image -- 强抓图片比较慢
                    if (m_devComm.m_nConnected == 2) {
                        w_nRet = m_devComm.Run_UpImage(0, m_binImage, w_nWidth, w_nHeight);

                        if (w_nRet != DevComm.ERR_SUCCESS) {
                            m_strPost = GetErrorMsg(w_nRet);
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runEnableCtrl);
                            return;
                        }

                        // Draw image
                        m_nImgWidth = w_nWidth[0];
                        m_nImgHeight = w_nHeight[0];
                        if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                        if (m_FpImageViewer != null) m_FpImageViewer.post(runDrawImage);
                    }

                    // Create Template
                    w_nRet = m_devComm.Run_Generate(w_nEnrollStep);

                    if (w_nRet != DevComm.ERR_SUCCESS) {
                        if (w_nRet == DevComm.ERR_BAD_QUALITY) {
                            m_strPost = "Bad quality. Try Again!";
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                            continue;
                        } else {
                            m_strPost = GetErrorMsg(w_nRet);
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runEnableCtrl);
                            return;
                        }
                    }
                    w_nEnrollStep++;
                }

                // Merge
                if (w_nGenCount != 1) {
                    //. Merge Template
                    w_nRet = m_devComm.Run_Merge(0, w_nGenCount);

                    if (w_nRet != DevComm.ERR_SUCCESS) {
                        m_strPost = GetErrorMsg(w_nRet);
                        if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                        if (m_FpImageViewer != null) m_FpImageViewer.post(runEnableCtrl);
                        return;
                    }
                }

                //. Store template
                w_nRet = m_devComm.Run_StoreChar(w_nUserID, 0, w_nDupID);

                if (w_nRet != DevComm.ERR_SUCCESS) {
                    if (w_nRet == DevComm.ERR_DUPLICATION_ID)
                        m_strPost = String.format("Result : Fail\r\nDuplication ID = %d", w_nDupID[0]);
                    else
                        m_strPost = GetErrorMsg(w_nRet);
                } else {
//                    m_strPost = String.format("Result : Success\r\nTemplate No : %d", m_nUserID);
                    m_strPost = context.getString(R.string.result_success);
                    //指纹录入完成
                    finishSB();
                    return;
                }

                if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                if (m_FpImageViewer != null) m_FpImageViewer.post(runEnableCtrl);

            }
        }).start();
    }

    //搜索指纹
    public void identify() {
        if (!m_devComm.IsInit())
            return;

        m_devComm.Run_SLEDControl(1);
        m_strPost = "";

        new Thread(new Runnable() {
            int w_nRet;
            int[] w_nID = new int[1];
            int[] w_nLearned = new int[1];
            int[] w_nWidth = new int[1];
            int[] w_nHeight = new int[1];

            @Override
            public void run() {

                while (true) {
                    if (m_strPost.isEmpty())
                        m_strPost = context.getString(R.string.input_your_finger);
//                    else
//                        m_strPost = m_strPost + "\n" + context.getString(R.string.input_your_finger);
                    if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);

                    if (Capturing() < 0)
                        return;

                    m_strPost = context.getString(R.string.release_your_finger);
                    if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);

                    // Up Cpatured Image
                    if (m_devComm.m_nConnected == 2) {
                        w_nRet = m_devComm.Run_UpImage(0, m_binImage, w_nWidth, w_nHeight);

                        if (w_nRet != DevComm.ERR_SUCCESS) {
                            m_strPost = GetErrorMsg(w_nRet);
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runEnableCtrl);
                            return;
                        }

                        // Draw image
                        m_nImgWidth = w_nWidth[0];
                        m_nImgHeight = w_nHeight[0];
                        if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                        if (m_FpImageViewer != null) m_FpImageViewer.post(runDrawImage);
                    }

                    // Create template
                    m_nPassedTime = SystemClock.elapsedRealtime();
                    w_nRet = m_devComm.Run_Generate(0);

                    if (w_nRet != DevComm.ERR_SUCCESS) {
                        m_strPost = GetErrorMsg(w_nRet);
                        if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);

                        if (w_nRet == DevComm.ERR_CONNECTION) {
                            if (m_FpImageViewer != null) m_FpImageViewer.post(runEnableCtrl);
                            return;
                        } else {
                            SystemClock.sleep(1000);
                            continue;
                        }
                    }

                    // Identify
                    w_nRet = m_devComm.Run_Search(0, 1, m_nMaxFpCount, w_nID, w_nLearned);
                    m_nPassedTime = SystemClock.elapsedRealtime() - m_nPassedTime;

                    if (w_nRet == DevComm.ERR_SUCCESS) {
                        zhiwenID = w_nID[0];
//                        m_strPost = String.format("Result : Success\r\nTemplate No : %d, Learn Result : %d\r\nMatch Time : %dms", w_nID[0], w_nLearned[0], m_nPassedTime);
                        m_strPost = context.getString(R.string.result_success);
                        finishSB();
                    } else {
                        m_strPost = String.format("\r\nMatch Time : %dms", m_nPassedTime);
                        m_strPost = GetErrorMsg(w_nRet) + m_strPost;
                    }
                }
            }
        }).start();
    }

    private void finishSB() {
        if (mHandler != null) {
            mHandler.sendEmptyMessage(FINISH_SB);
        }
    }

    public Handler mHandler = null;
    public int zhiwenID = 0; //识别的指纹ID
    public static final int FINISH_SB = 1666; //成功完成识别

    //删除用户ID的指纹模板
    public boolean deleteID() {
        int w_nRet;

        if (!m_devComm.IsInit())
            return false;

        if (!CheckUserID())
            return false;

        w_nRet = m_devComm.Run_DelChar(m_nUserID, m_nUserID);

        if (w_nRet != DevComm.ERR_SUCCESS) {
            showMsg(GetErrorMsg(w_nRet));
            return false;
        }

        showMsg("Delete OK !");
        return true;
    }

    //清空指纹库
    public void deleteAll() {
        int w_nRet;

        if (!m_devComm.IsInit())
            return;

        w_nRet = m_devComm.Run_DelChar(1, m_nMaxFpCount);

        if (w_nRet != DevComm.ERR_SUCCESS) {
            showMsg(GetErrorMsg(w_nRet));
            return;
        }
        showMsg("Delete all OK !");
    }

    //上传特征 --- 将一个ID的指纹保存到磁盘
    private void getTemplate() {
        int w_nRet;
        byte[] w_pTemplate = new byte[DevComm.MAX_DATA_LEN];

        // Check Device Connection
        if (!m_devComm.IsInit())
            return;

        // Check User ID
        if (!CheckUserID())
            return;

        do {
            // Load Template to Buffer
            w_nRet = m_devComm.Run_LoadChar((short) m_nUserID, 0);
            if (w_nRet != DevComm.ERR_SUCCESS) {
                showMsg(GetErrorMsg(w_nRet));
                break;
            }

            // Up Template
            w_nRet = m_devComm.Run_UpChar(0, w_pTemplate);
            if (w_nRet != DevComm.ERR_SUCCESS) {
                showMsg(GetErrorMsg(w_nRet));
                break;
            }

            ////////////////////////////////////////////////////////////////////
            // Save Template (/FPData/01.fpt)
            // Create Directory
            String w_szSaveDirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/FPData";
            File w_fpDir = new File(w_szSaveDirPath);
            if (!w_fpDir.exists())
                w_fpDir.mkdirs();

            // Create Template File
            File w_fpTemplate = new File(w_szSaveDirPath + "/" + String.valueOf(m_nUserID) + ".fpt");
            if (!w_fpTemplate.exists()) {
                try {
                    w_fpTemplate.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            // Save Template Data
            FileOutputStream w_foTemplate = null;
            try {
                w_foTemplate = new FileOutputStream(w_fpTemplate);
                w_foTemplate.write(w_pTemplate, 0, DevComm.MAX_DATA_LEN);
                w_foTemplate.close();
                // Show Status
                showMsg(String.format("Result : Get Template Success.\r\nDir : %s", w_szSaveDirPath + "/" + String.valueOf(m_nUserID) + ".fpt"));
                break;
            } catch (Exception e) {
                e.printStackTrace();
            }
            ////////////////////////////////////////////////////////////////////

            showMsg(String.format("Result : Get Template Success.\r\nSave Template Failed."));
        } while (false);

    }

    //下载特征 --- 将指纹库写到串口
    private void setTemplate() {
        int w_nRet;
        int[] w_nDupTmplNo = new int[1];
        byte[] w_pTemplate = new byte[DevComm.MAX_DATA_LEN];

        // Check Device Connection
        if (!m_devComm.IsInit())
            return;

        // Check User ID
        if (!CheckUserID())
            return;

        ////////////////////////////////////////////////////////////////////
        // Load Template (/FPData/01.fpt)
        // Check Directory
        String w_szLoadDirPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/FPData";
        File w_fpDir = new File(w_szLoadDirPath);
        if (!w_fpDir.exists()) {
            showMsg(String.format("Result : Can't load template data.\r\nDir : %s", w_szLoadDirPath + "/" + String.valueOf(m_nUserID) + ".fpt"));
            return;
        }

        // Check Template File
        File w_fpTemplate = new File(w_szLoadDirPath + "/" + String.valueOf(m_nUserID) + ".fpt");
        if (!w_fpTemplate.exists()) {
            showMsg(String.format("Result : Can't load template data.\r\nDir : %s", w_szLoadDirPath + "/" + String.valueOf(m_nUserID) + ".fpt"));
            return;
        }

        // Load Template Data
        FileInputStream w_fiTemplate = null;
        try {
            w_fiTemplate = new FileInputStream(w_fpTemplate);
            w_fiTemplate.read(w_pTemplate, 0, DevComm.MAX_DATA_LEN);
            w_fiTemplate.close();
        } catch (Exception e) {
            e.printStackTrace();
            showMsg(String.format("Result : Can't load template data.\r\nDir : %s", w_szLoadDirPath + "/" + String.valueOf(m_nUserID) + ".fpt"));
            return;
        }
        ////////////////////////////////////////////////////////////////////

        do {
            // Download Template to Buffer
            w_nRet = m_devComm.Run_DownChar(0, w_pTemplate);
            if (w_nRet != DevComm.ERR_SUCCESS) {
                m_txtStatus.setText(GetErrorMsg(w_nRet) + "Run_DownChar");
                break;
            }

            // Store Template
            w_nRet = m_devComm.Run_StoreChar(m_nUserID, 0, w_nDupTmplNo);
            if (w_nRet != DevComm.ERR_SUCCESS) {
                if (w_nRet == DevComm.ERR_DUPLICATION_ID) {
                    showMsg(String.format("Result : Fail\r\nDuplication ID = %d", w_nDupTmplNo[0]));
                } else {
                    showMsg(GetErrorMsg(w_nRet) + "Run_StoreChar");
                }
                break;
            }

            showMsg(String.format("Result : Set Template Success.\r\nUserID = %d", m_nUserID));
        } while (false);


    }

    private boolean CheckUserID() {
        if (m_nUserID < 1) {
            return false;
        }
        return true;
    }

    private int Capturing() {
        int w_nRet;
        while (true) {
            if (mNeedbreak) {
                return -1;
            }
            w_nRet = m_devComm.Run_GetImage();
            if (w_nRet == DevComm.ERR_CONNECTION) {
                m_strPost = "Communication error!";
                if (m_FpImageViewer != null) m_FpImageViewer.post(runShowStatus);
                if (m_FpImageViewer != null) m_FpImageViewer.post(runEnableCtrl);
                return -1;
            } else if (w_nRet == DevComm.ERR_SUCCESS) {
                break;
            }
        }

        return 0;
    }

    public boolean mNeedbreak = false;//需要跳出指纹识别的线程

    private String GetErrorMsg(int nErrorCode) {
        String str = new String("");

        switch (nErrorCode) {
            case DevComm.ERR_SUCCESS:
                str = "Succcess";
                break;
            case DevComm.ERR_VERIFY:
                str = "Verify NG";
                break;
            case DevComm.ERR_IDENTIFY:
                str = "Identify NG";
                break;
            case DevComm.ERR_EMPTY_ID_NOEXIST:
                str = "Empty Template no Exist";
                break;
            case DevComm.ERR_BROKEN_ID_NOEXIST:
                str = "Broken Template no Exist";
                break;
            case DevComm.ERR_TMPL_NOT_EMPTY:
                str = "Template of this ID Already Exist";
                break;
            case DevComm.ERR_TMPL_EMPTY:
                str = "This Template is Already Empty";
                break;
            case DevComm.ERR_INVALID_TMPL_NO:
                str = "Invalid Template No";
                break;
            case DevComm.ERR_ALL_TMPL_EMPTY:
                str = "All Templates are Empty";
                break;
            case DevComm.ERR_INVALID_TMPL_DATA:
                str = "Invalid Template Data";
                break;
            case DevComm.ERR_DUPLICATION_ID:
                str = "Duplicated ID : ";
                break;
            case DevComm.ERR_BAD_QUALITY:
                str = "Bad Quality Image";
                break;
            case DevComm.ERR_MERGE_FAIL:
                str = "Merge failed";
                break;
            case DevComm.ERR_NOT_AUTHORIZED:
                str = "Device not authorized.";
                break;
            case DevComm.ERR_MEMORY:
                str = "Memory Error ";
                break;
            case DevComm.ERR_INVALID_PARAM:
                str = "Invalid Parameter";
                break;
            case DevComm.ERR_GEN_COUNT:
                str = "Generation Count is invalid";
                break;
            case DevComm.ERR_INVALID_BUFFER_ID:
                str = "Ram Buffer ID is invalid.";
                break;
            case DevComm.ERR_INVALID_OPERATION_MODE:
                str = "Invalid Operation Mode!";
                break;
            case DevComm.ERR_FP_NOT_DETECTED:
                str = "Finger is not detected.";
                break;
            default:
                str = String.format("Fail, error code=%d", nErrorCode);
                break;
        }

        return str;
    }

    Runnable runShowStatus = new Runnable() {
        public void run() {
            showMsg(m_strPost);
        }
    };

    Runnable runDrawImage = new Runnable() {
        public void run() {
            int nSize;

            MakeBMPBuf(m_binImage, m_bmpImage, m_nImgWidth, m_nImgHeight);

            if ((m_nImgWidth % 4) != 0)
                nSize = m_nImgWidth + (4 - (m_nImgWidth % 4));
            else
                nSize = m_nImgWidth;

            nSize = 1078 + nSize * m_nImgHeight;

            //DebugManage.WriteBmp(m_bmpImage, nSize);

            Bitmap image = BitmapFactory.decodeByteArray(m_bmpImage, 0, nSize);

            if (m_FpImageViewer != null) m_FpImageViewer.setImageBitmap(image);
        }
    };

    Runnable runEnableCtrl = new Runnable() {
        public void run() {
            m_devComm.Run_SLEDControl(0);
        }
    };

    private void MakeBMPBuf(byte[] Input, byte[] Output, int iImageX, int iImageY) {

        byte[] w_bTemp = new byte[4];
        byte[] head = new byte[1078];
        byte[] head2 = {
                /***************************/
                //file header
                0x42, 0x4d,//file type
                //0x36,0x6c,0x01,0x00, //file size***
                0x0, 0x0, 0x0, 0x00, //file size***
                0x00, 0x00, //reserved
                0x00, 0x00,//reserved
                0x36, 0x4, 0x00, 0x00,//head byte***
                /***************************/
                //infoheader
                0x28, 0x00, 0x00, 0x00,//struct size

                //0x00,0x01,0x00,0x00,//map width***
                0x00, 0x00, 0x0, 0x00,//map width***
                //0x68,0x01,0x00,0x00,//map height***
                0x00, 0x00, 0x00, 0x00,//map height***

                0x01, 0x00,//must be 1
                0x08, 0x00,//color count***
                0x00, 0x00, 0x00, 0x00, //compression
                //0x00,0x68,0x01,0x00,//data size***
                0x00, 0x00, 0x00, 0x00,//data size***
                0x00, 0x00, 0x00, 0x00, //dpix
                0x00, 0x00, 0x00, 0x00, //dpiy
                0x00, 0x00, 0x00, 0x00,//color used
                0x00, 0x00, 0x00, 0x00,//color important
        };

        int i, j, num, iImageStep;

        Arrays.fill(w_bTemp, (byte) 0);

        System.arraycopy(head2, 0, head, 0, head2.length);

        if ((iImageX % 4) != 0)
            iImageStep = iImageX + (4 - (iImageX % 4));
        else
            iImageStep = iImageX;

        num = iImageX;
        head[18] = (byte) (num & (byte) 0xFF);
        num = num >> 8;
        head[19] = (byte) (num & (byte) 0xFF);
        num = num >> 8;
        head[20] = (byte) (num & (byte) 0xFF);
        num = num >> 8;
        head[21] = (byte) (num & (byte) 0xFF);

        num = iImageY;
        head[22] = (byte) (num & (byte) 0xFF);
        num = num >> 8;
        head[23] = (byte) (num & (byte) 0xFF);
        num = num >> 8;
        head[24] = (byte) (num & (byte) 0xFF);
        num = num >> 8;
        head[25] = (byte) (num & (byte) 0xFF);

        j = 0;
        for (i = 54; i < 1078; i = i + 4) {
            head[i] = head[i + 1] = head[i + 2] = (byte) j;
            head[i + 3] = 0;
            j++;
        }

        System.arraycopy(head, 0, Output, 0, 1078);

        if (iImageStep == iImageX) {
            for (i = 0; i < iImageY; i++) {
                System.arraycopy(Input, i * iImageX, Output, 1078 + i * iImageX, iImageX);
            }
        } else {
            iImageStep = iImageStep - iImageX;

            for (i = 0; i < iImageY; i++) {
                System.arraycopy(Input, i * iImageX, Output, 1078 + i * (iImageX + iImageStep), iImageX);
                System.arraycopy(w_bTemp, 0, Output, 1078 + i * (iImageX + iImageStep) + iImageX, iImageStep);
            }
        }
    }

    private final IUsbConnState m_IConnectionHandler = new IUsbConnState() {
        @Override
        public void onUsbConnected() {
            String[] w_strInfo = new String[1];
            if (m_devComm.Run_TestConnection() == DevComm.ERR_SUCCESS) {
                if (m_devComm.Run_GetDeviceInfo(w_strInfo) == DevComm.ERR_SUCCESS) {
                    showMsg("Open Success!\r\nDevice Info : " + w_strInfo[0]);
                }
            } else {
                showMsg("Can not connect to device!");
            }

        }

        @Override
        public void onUsbPermissionDenied() {

            showMsg("Permission denied!");

        }

        @Override
        public void onDeviceNotFound() {

            showMsg("Can not find usb device!");

        }
    };

}
